package com.xxl.job.executor.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ruoyi.bizsys.domain.LoanDeferredOrder;
import com.ruoyi.bizsys.domain.LoanIou;
import com.ruoyi.bizsys.domain.LoanOrder;
import com.ruoyi.bizsys.domain.RepaymentOrder;
import com.ruoyi.bizsys.service.ILoanDeferredOrderService;
import com.ruoyi.bizsys.service.ILoanIouService;
import com.ruoyi.bizsys.service.IRepaymentOrderService;
import com.ruoyi.bizsys.utils.BizConstants;
import com.ruoyi.business.component.RepaymentMqSendService;
import com.ruoyi.business.service.IBLoanInfoService;
import com.ruoyi.business.service.IDoRepaymentLastService;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.enums.RspEnmu;
import com.ruoyi.common.exception.BusinessException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.xxl.job.executor.service.WalletDebitService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

/**
 * @Author: jamnsin
 * @Date: 2022/8/15 23:47
 */
@Slf4j
@Service
public class WalletDebitServiceImpl implements WalletDebitService {

    @Autowired
    ILoanIouService loanIouService;

    @Autowired
    ILoanDeferredOrderService loanDeferredOrderService;

    @Autowired
    IDoRepaymentLastService doRepaymentLastService;

    @Autowired
    IBLoanInfoService bLoanInfoService;

    @Autowired
    IRepaymentOrderService repaymentOrderService;

    @Autowired
    RepaymentMqSendService repaymentMqService;

    @Autowired
    RedisCache redisCache;

    @Override
    public void walletDebit(String custNo) {
        log.info("[钱包代扣 {}] 开始处理客户 >>> ", custNo);

        String loanSerialNo = null;

        String lockKey = RedisCache.CUST_ACCOUNT_LOCK_KEY_PREFIX + custNo;
        RLock lock = redisCache.lock(lockKey);
        try {
            long start = System.currentTimeMillis();

            // 查询在贷借据
            List<LoanIou> loanIous = loanIouService.getCustDueIou(custNo);
            for (LoanIou loanIou : loanIous) {
                loanSerialNo = loanIou.getLoanSerialNo();

                log.info("[钱包代扣 {}] 开始处理借据 >>> {}", custNo, loanSerialNo);
                if (!isIouDue(loanIou)) continue;

                if (isDeferredPending(loanIou.getLoanSerialNo())) continue;

                BigDecimal amountDue = LoanIou.calculationOverdueAmount(loanIou);
                JSONObject walletAmtObj = doRepaymentLastService.getAmtWallet(amountDue, loanIou.getCustNo(), loanIou.getLoanSerialNo(), false);

                BigDecimal amtWallet = walletAmtObj.getBigDecimal("amtWallet");
                if (amtWallet.compareTo(BigDecimal.ZERO) == 0) {
                    log.info("[钱包代扣 {}] 客户钱包可用余额为 0，中止处理客户借据", custNo);
                    break;
                }

                sendWalletDebitMq(loanIou, amountDue, amtWallet, walletAmtObj.getString("walletOrderNo"));

                log.info("[钱包代扣 {}] 结束处理借据 >>> loanSerialNo = {}", custNo, loanSerialNo);
            }

            log.info("[钱包代扣 {}] 锁占用时间，耗时：{} ms", custNo, (System.currentTimeMillis() - start));
        } catch (Exception e) {
            log.error("[钱包代扣 {}] 处理客户异常 >>> loanSerialNo = {}：{}", custNo, loanSerialNo, e.getMessage(), e);
            throw e;
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }

        log.info("[钱包代扣 {}] 结束处理客户 >>> ", custNo);
    }

    @Override
    public void walletDebitIou(List<String> loanSerialNos) {
        for (String loanSerialNo : loanSerialNos) {
            log.info("开始处理借据 >>> {}", loanSerialNo);

            LoanIou preLoanIou = loanIouService.getOne(new QueryWrapper<LoanIou>().select("cust_no").eq("loan_serial_no", loanSerialNo).eq("state", BizConstants.BIZ_STATUS_1).last("limit 1"));
            try {
                String lockKey = RedisCache.CUST_ACCOUNT_LOCK_KEY_PREFIX + preLoanIou.getCustNo();
                RLock lock = redisCache.lock(lockKey);
                long start = System.currentTimeMillis();

                try {
                    LoanIou loanIou = loanIouService.getOne(new QueryWrapper<LoanIou>().eq("loan_serial_no", loanSerialNo).eq("state", BizConstants.BIZ_STATUS_1).last("limit 1"));

                    if (!isIouDue(loanIou)) continue;
                    if (isDeferredPending(loanIou.getLoanSerialNo())) continue;

                    BigDecimal amountDue = LoanIou.calculationOverdueAmount(loanIou);
                    JSONObject walletAmtObj = doRepaymentLastService.getAmtWallet(amountDue, loanIou.getCustNo(), loanIou.getLoanSerialNo(), false);

                    BigDecimal amtWallet = walletAmtObj.getBigDecimal("amtWallet");
                    if (amtWallet.compareTo(BigDecimal.ZERO) == 0) {
                        log.info("客户钱包可用余额为0，中止处理客户借据，custNo = {}", preLoanIou.getCustNo());
                        break;
                    }

                    sendWalletDebitMq(loanIou, amountDue, amtWallet, walletAmtObj.getString("walletOrderNo"));
                } catch (Exception e) {
                    log.error("[钱包代扣] 处理借据异常「{}」：{}", loanSerialNo, e.getMessage(), e);
                } finally {
                    if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                        lock.unlock();
                    }
                }
                log.info("锁占用时间，耗时：{} ms", (System.currentTimeMillis() - start));
            } catch (Exception e) {
                log.error("[钱包代扣] 处理借据 Error「{}」：{}", loanSerialNo, e.getMessage(), e);
            }

            log.info("结束处理借据 >>> {}", loanSerialNo);
        }
    }

    private void sendWalletDebitMq(LoanIou loanIou, BigDecimal amountDue, BigDecimal amtWallet, String walletOrderNo) {

        RepaymentOrder repaymentOrder = createRepaymentOrder(loanIou, amountDue, amtWallet, walletOrderNo);
        try {
            SendResult sendResult = repaymentMqService.selfWalletForAllPayRepaymentMq(loanIou, repaymentOrder);
            if (!(SendStatus.SEND_OK).equals(sendResult.getSendStatus())) {
                log.info("[钱包代扣] 发送 MQ 失败，loanSerialNo = {}，sendResult = {}", loanIou.getLoanSerialNo(), sendResult);
                throw new BusinessException(RspEnmu.MQ_SEND_FAILED);
            }
        } catch (Exception e) {
            log.error("[钱包代扣] 发送 MQ 失败，errMsg = {}", e.getMessage(), e);
            udpateRepaymentOrderFaild(repaymentOrder, "MQ 发送失败");
            doRepaymentLastService.updateWalletOrderToFailed(walletOrderNo, false);
        }
    }

    private RepaymentOrder createRepaymentOrder(LoanIou loanIou, BigDecimal amountDue, BigDecimal amtWallet, String walletOrderNo) {
        RepaymentOrder repaymentOrder = bLoanInfoService.createRepaymentOrderRepayAllWalletNoSuccess(loanIou, BizConstants.LOAN_REPAYMENT_ORDER_TYPE_WALLET_BATCH_DEBIT, "", amountDue, "-1");
        repaymentOrder.setRepaymentAmt(amtWallet);
        repaymentOrder.setPayTransSerialNo(walletOrderNo);
        repaymentOrderService.updateById(repaymentOrder);
        return repaymentOrder;
    }

    private void udpateRepaymentOrderFaild(RepaymentOrder repaymentOrder, String remark) {
        repaymentOrder.setRepayStatus(BizConstants.REPAYMENT_ORDER_STATUS_FAIL);
        repaymentOrder.setRemark(remark);
        repaymentOrderService.updateById(repaymentOrder);
    }

    private static boolean isIouDue(LoanIou loanIou) {
        if (loanIou.getIouStatus() == 2) {
            log.info("{} 借据逾期", loanIou.getLoanSerialNo());
            return true;
        }

        String currentDate = DateFormatUtils.format(new Date(), DateUtils.YYYY_MM_DD_SLASH);
        if ((loanIou.getIsDeferred() == null || loanIou.getIsDeferred() == 0) && StringUtils.equals(currentDate, loanIou.getRepayDate())) {
            log.info("{} 借据到期", loanIou.getLoanSerialNo());
            return true;
        }

        if (loanIou.getIsDeferred() != null && loanIou.getIsDeferred() == 1 && StringUtils.equals(currentDate, loanIou.getDeferredRepayDate())) {
            log.info("{} 延期借据到期", loanIou.getLoanSerialNo());
            return true;
        }

        log.info("{} 借据未到期，跳过", loanIou.getLoanSerialNo());
        return false;
    }

    private boolean isDeferredPending(String loanSerialNo) {
        boolean flag = loanDeferredOrderService.count(new QueryWrapper<LoanDeferredOrder>().eq("loan_serial_no", loanSerialNo).eq("status", LoanOrder.LOAN_STATUS_9_APPROVALING).eq("state", BizConstants.BIZ_STATUS_1)) > 0;
        if (flag) {
            log.info("借据延期申请在途，不处理，跳过");
        }
        return flag;
    }
}
